package GameModel;

import java.util.ArrayList;
import java.util.Stack;

import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

import Player.mPlayer;
import Server.mServerSessions;

@Root
/**
 * 
 * @author David Zimmerli
 *
 */
public class mGame {
	public enum eGameState {
		waitingForPlayers, GameIsRunning, GameIsFinished
	}

	static boolean StrictGamePlay = true;

	static int GameIncrement = 0;
	protected int GameRoundIncrement;

	@Attribute
	private int GameID;
	@Element(name = "MaxPoints")
	private int MaxPoints = 0;
	@Element(name = "PlayerNumber")
	private int PlayerNumber = 0; // 2er oder 3er?
	@Element(name = "GameState")
	private eGameState GameState = eGameState.waitingForPlayers;
	@Element(name = "GameName")
	private String GameName = "";
	@Element(name = "CurrentPlayer")
	private mPlayer CurrentPlayer;
	// @Element(name = "Winner")
	private mPlayer Winner;
	// @ElementList(name="PlayerInGameList", required=false)
	// private ArrayList<mPlayer> PlayerInGameList = new ArrayList<mPlayer>();
	// @ElementList(name="PlayerList", required=false)
	// private ArrayList<mPlayer> PlayerList = new ArrayList<mPlayer>();
	// @ElementList(name="GameRoundList", required=false)
	@ElementList
	ArrayList<mGameStat> PlayerInGameKonto = new ArrayList<mGameStat>();
	private Stack<mGameRound> GameRoundList = new Stack<mGameRound>();
	// private int End;

	// @ElementList
	Stack<mCardCombo> CardListList = new Stack<mCardCombo>(); // Was ist das? by
																// CV.

	// @Element
	// private mChat Chat = new mChat();

	/**
	 * Erstellt dem Spieler ein Spielerkonto im Game Muss synchronisiert werden
	 * um gleichzeitiges Joinen und somit das ï¿½berschreiten der maximalen
	 * Spieleranzahl vorzubeugen!
	 * 
	 * @param PlayerStat
	 * @return false / true
	 * @see mServerSessions - case joinGame:
	 */

	public synchronized boolean isValidNextCombo(ArrayList<mCard> arrayList) {
		return this.getLastTrickPlay().isValidNextCombo(arrayList,
				StrictGamePlay);
	}

	public synchronized boolean addPlayerKonto(mPlayer Player) {
//		System.out.println("Playernumber: " + PlayerNumber
//				+ " PlayerInGameKonto: " + PlayerInGameKonto);

		if (PlayerNumber + 1 > PlayerInGameKonto.size()
				&& this.getPlayerKonto(Player) == null) {
			// System.out.println("Adding new mGameStat");
			PlayerInGameKonto.add(new mGameStat(Player));
			// System.out.println("Playernumber: " + PlayerNumber
			// + " PlayerInGameKonto: " + PlayerInGameKonto);

			if (PlayerNumber == PlayerInGameKonto.size()) {
				// System.out.println("Starting new game");
				// System.out.println("Playernumber: " + PlayerNumber
				// + " PlayerInGameKonto: " + PlayerInGameKonto);
				GameStart();

			}
			return true;
		} else {
			return false;
		}

	}

	/**
	 * ï¿½berprï¿½ft ob es mï¿½glich ist einen weiteren User hinzuzufï¿½gen,
	 * ohne ihn dann auch wirklich hinzuzufï¿½gen!
	 * 
	 * @param Player
	 * @return false / true
	 * @see mServerSessions - case joinGame:
	 */

	public synchronized boolean isPossible2addPlayerKonto(mPlayer Player) {
		System.out.println("Playernumber: " + PlayerNumber
				+ " PlayerInGameKonto: " + PlayerInGameKonto);

		if (PlayerNumber > PlayerInGameKonto.size()
				&& this.getPlayerKonto(Player) == null) {
			return true;
		} else {
			return false;
		}

	}

	/**
	 * Probiert einen Player zu lï¿½schen. Wenn geglï¿½ckt (spiel ist noch nicht
	 * gestartet!), dann gibt es wahr zurï¿½ck, sonst falsch Muss synchronisiert
	 * werden gleichzeitiges verlassen zu umgehen!
	 * 
	 * @param Player
	 * @return false / true
	 * @see mServerSessions - case joinGame:
	 */
	public synchronized boolean removePlayerKonto(mPlayer Player) {
		try {
			if (this.GameState == eGameState.GameIsFinished
					&& this.getPlayers().size() > 1) {
				PlayerInGameKonto.remove(Player);
				return true;
			} else {
				PlayerInGameKonto.remove(Player);
				this.isFinishedGame();
				return false;
			}
		} catch (Exception e) {
			PlayerInGameKonto.remove(Player);
			this.isFinishedGame();
			return false;
		}
	}

	/**
	 * Alle benï¿½tigten Spieler sind im Spiel Die Gameorunde wird
	 * initialisiert, und setzt GameState auf Running
	 * 
	 * @see addPlayerKonto()
	 * 
	 */

	private void GameStart() {
		this.startNewGameRound();
		this.GameState = eGameState.GameIsRunning;
	}

	void isNewRound() {
		this.startNewGameRound();

	}

	/**
	 * Neue Game wird erzeugt vom Spielersteller. Die Informationen kommen vom
	 * Client.
	 * 
	 * @param GameName
	 * @param PlayerNumber
	 * @param maxPoints
	 * @param Session
	 * @return
	 */

	// Ein Neue Spiel wird erstellt, der Handler gibt die Informationen an mGame
	// weiter. Die Game Runde wird erstellt.
	public mGame(String GameName, int PlayerNumber, int maxPoints,
			mPlayer Player) {
		GameIncrement++;
		this.GameID = GameIncrement;
		this.GameName = GameName;
		this.MaxPoints = maxPoints;
		this.GameState = GameState.waitingForPlayers;
		this.PlayerNumber = PlayerNumber;
		PlayerInGameKonto.add(new mGameStat(Player));
		this.CurrentPlayer = Player;

	}

	/**
	 * 
	 * 
	 * @param cardCombo
	 * @param player
	 * @return true - wennn Combo gï¿½ltig ist
	 * @return false - wenn Combo falsch, oder der Spieler nicht an der Reihe
	 *         ist
	 * @see Server.mServerSessions - case NewCardCombo
	 */

	public boolean newCardCombo(ArrayList<mCard> cardCombo, mPlayer player) {
		if (player.equals(this.getCurrentPlayer())) { // Fï¿½ngt Manipulationen
														// Ab... es kann nur der
														// aktuelle Spieler
														// spielen
//			System.out.println("wird vom richtigen gespielt!");
			return this.getLastTrickPlay().newCardCombo(cardCombo, player,
					StrictGamePlay);
		} else {
			return false;
		}
	}

	public void setHandCards(ArrayList<mCard> handCards, mPlayer Player) {
		getPlayerKonto(Player).setPlayerCards(handCards);

	}

	public void setHandCards(ArrayList<ArrayList<mCard>> AL) {
		try {
			int i = 1;
			for (ArrayList<mCard> hcOfP : AL) {
				this.getPlayerKontoByNumber(i).setPlayerCards(hcOfP);
				i++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public ArrayList<ArrayList<mCard>> getHandCards() {
		ArrayList<ArrayList<mCard>> al = new ArrayList<ArrayList<mCard>>(3);
		for (mGameStat gs : this.getPlayerIngameKonto()) {
			al.add(gs.getPlayerCards());
		}
		return al;
	}

	public ArrayList<mCard> getHaggis() {
		try {
			return this.getLastGameRound().getHaggis();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public void setHaggis(ArrayList<mCard> haggis) {
		try {
			this.getLastGameRound().setHaggis(haggis);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public String toString() {
		return "mGame [GameRoundIncrement=" + GameRoundIncrement
				+ ", MaxPoints=" + MaxPoints + ", PlayerNumber=" + PlayerNumber
				+ ", GameState=" + GameState + ", GameName=" + GameName
				+ ", PlayerInGameKonto=" + PlayerInGameKonto
				+ ", GameRoundList=" + GameRoundList + ", CardListList="
				+ CardListList + "]";
	}

	public ArrayList<mGameStat> getPlayerIngameKonto() {
		return PlayerInGameKonto;
	}

	public ArrayList<mPlayer> getPlayers() {
		ArrayList<mPlayer> players = new ArrayList<mPlayer>(3);
		for (mGameStat konto : getPlayerIngameKonto()) {
			players.add(konto.getPlayer());
		}
		return players;
	}

	public ArrayList<mCard> getHandCardsByPlayer(mPlayer player) {
		for (mGameStat gs : this.PlayerInGameKonto) {
			if (gs.getPlayer().equals(player))
				return gs.getPlayerCards();
		}
		return null;
	}

	public ArrayList<mCard> getJokerCardsByPlayer(mPlayer player) {
		for (mGameStat gs : this.PlayerInGameKonto) {
			if (gs.getPlayer().equals(player))
				return gs.getJokerCards();
		}
		return null;
	}

	private void startNewGameRound() {
		this.GameRoundList.add(new mGameRound(false, false, this));
	}

	public mGameRound getLastGameRound() {
		return this.GameRoundList.peek();
	}

	public mGameTrickPlay getLastTrickPlay() {
		return this.getLastGameRound().getLastTrickPlay();
	}

	public mPlayer getCurrentPlayer() {
		return this.CurrentPlayer;
	}

	public void setCurrentPlayer(mPlayer CurrentPlayer) {
		this.CurrentPlayer = CurrentPlayer;
	}

	public void setWinner(mPlayer Winner) {
		this.Winner = Winner;
	}

	public mPlayer getWinner() {
		return this.Winner;
	}

	public mCardCombo getLastCardCombo() {
		return this.getLastTrickPlay().getLastCardCombo();
	}

	/**
	 * 
	 * 
	 * @param Player
	 * @see mGardCombo.checkComboCards(); noCardList(mPlayer Player, mGame
	 *      mGame);
	 */
	public mPlayer nextPlayer(mPlayer Player) {
		return nextPlayer(Player, true);
	}

	public mPlayer nextPlayer(mPlayer Player, boolean setCurrentPlayer) {

		int pkn = this.getPlayerKontoNumber(Player);
		int Newpkn = 5;
		mPlayer nextPlayer = null;

		// Wenn ein Spieler Keine Karten mehr hat, soll er ausgelassen werden

		System.out.println("Nextplayer -PlayerOutOfCards: " + pkn);
		if (this.getLastGameRound().getNumberPlayerOutOfCards() == 0) {
			switch (pkn) {
			case 0:
				nextPlayer = this.getPlayerIngameKonto().get(1).getPlayer();

				break;

			case 1:
				//System.out.println("Nextplayer -PlayerOutOfCards:  Case1");
				if (this.getPlayerNumber() == 2) {
					nextPlayer = this.getPlayerIngameKonto().get(0).getPlayer();
				} else {
					nextPlayer = this.getPlayerIngameKonto().get(2).getPlayer();
				}

				break;

			case 2:
				nextPlayer = this.getPlayerIngameKonto().get(0).getPlayer();
				System.out.println("Nextplayer -PlayerOutOfCards:  Case2 ");
				break;

			}// END switch(pkn)
		} else {

			//System.out.println("Nextplayer - Switch - AktuellPlayer: " + pkn);

			switch (pkn) {
			case 0:
				Newpkn = 1;

				break;
			case 1:
				if (this.getPlayerNumber() == 2) {
					Newpkn = 0;
				} else {
					Newpkn = 2;
				}
				break;
			case 2:
				Newpkn = 0;

				break;

			} // END Switch (pnk)
			//System.out.println("Nextplayer bevor:" + Newpkn);
			if (Newpkn == this.getPlayerKontoNumber(this.getLastGameRound()
					.getRoundWinner())) {
				if (Newpkn != 2) {
					Newpkn = Newpkn + 1;
					System.out.println("Nextplayer  if !2:" + Newpkn);

				} else {
					Newpkn = 0;
					System.out.println("Nextplayer else:" + Newpkn);
				} // END if(Newpkn != 2)

			} // END if (Newpkn == this.getPlayerKontoNumber......)

//			System.out.println("next Player: "
//					+ this.getPlayerIngameKonto().get(Newpkn).getPlayer());
			nextPlayer = this.getPlayerIngameKonto().get(Newpkn).getPlayer();
		}// END if(this.getLastGameRound().getNumberPlayerOutOfCards() == 0)

//		System.out.println("Current-Player:" + this.getCurrentPlayer());
//		System.out.println("nextPlayer: " + nextPlayer);
		if (setCurrentPlayer) {
			this.setCurrentPlayer(nextPlayer);
		}
		return nextPlayer;

	}

	boolean isTrickFish() {
		if (this.getLastTrickPlay().getTrickEnd()) {
			return true;
		}

		return false;
	}

	/**
	 * 
	 * Ermittelt ob jemand oder mehrere die Siegpunkte erreicht hat
	 * 
	 * @return false = keiner hat die Siegpunktzahl ï¿½bertroffen, oder mind.
	 *         zwei haben die Siegpunktzahl ï¿½bertroffen, jedoch haben beide
	 *         die gleiche Punktzahlt => Entscheidungsrunde
	 * @return true = einer alleine hat die die Siegpunktzahl ï¿½bertroffen und
	 *         hat am meisten Punkte
	 * 
	 * 
	 */
	public boolean isFinishedGame() {
//		System.out.println("Begin Game is Finished");
		int MaxPoints = this.getMaxPoints();
		ArrayList<Integer> higherOrSame = new ArrayList<Integer>();
		int highestScore = 0;
		int players = this.getPlayerNumber();

		for (int i = 0; i < players; i++) {
			int PlayerPoints = this.getPlayerIngameKonto().get(i)
					.getGamePoints();
//			System.out.println("Finish Game - Schlaufe");
			// Wer hat hï¿½her als die Siegpunktzahl
			if (MaxPoints <= PlayerPoints) {
				System.out.println("Mind. ein Spieeler hat mehr Punkte");
				// Ist hï¿½her als die Siegpunkzahl
				// Fï¿½ge den Spieler zu SamePunktzahl
				if (highestScore < PlayerPoints) {
					highestScore = PlayerPoints;
					higherOrSame.clear();
					higherOrSame.add(i);
				} else if (highestScore == PlayerPoints) {
					higherOrSame.add(i);
				}
			}
		} // Ende for (int i = 0; i< players; i++)
//		System.out.println(higherOrSame.size()
//				+ " Spieler sind über der Gewinnpunktzahl");
		if (higherOrSame.size() == 0) {
			return false;
		} else if (higherOrSame.size() == 1) {
			this.setWinner(this.getPlayerIngameKonto().get(higherOrSame.get(0))
					.getPlayer());
			return true;
		} else if (higherOrSame.size() > 1) {
			return false;
		}

		return false;

	} // END IST FINISH Game

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + GameID;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		mGame other = (mGame) obj;
		if (GameID != other.GameID)
			return false;
		return true;
	}

	/**
	 * 
	 * 
	 * @see mGameRound.isFinishedRound()
	 */

	public void FinishGame() {
		this.GameState = GameState.GameIsFinished;
		for(int i = 0; i < this.getPlayerNumber(); i++)
		this.removePlayerKonto(this.getPlayerIngameKonto().get(i).getPlayer());
	}

	/**
	 * Fï¿½gt einen Spieler
	 * 
	 * 
	 * @param sender
	 * @return false
	 * @return true
	 */

	public boolean addPassPlayer(mPlayer sender) {
//		try {
////			System.out.println("Start Pass");
////			System.out.println("Karten: "
////					+ this.getLastCardCombo().getComboType()); // ComboNumber
//		} catch (Exception e) {
//			e.printStackTrace();// =
//		}
//		// 0
		if (sender.equals(this.getCurrentPlayer())) { // ï¿½berprï¿½ft ob dieser
														// Spieler berechtigt
														// ist fï¿½r den
														// Spielzug

			try {
				return this.getLastCardCombo().addPassPlayer(sender, this);
			} catch (Exception e) {
//				System.out.println("Execption pass!");
				e.printStackTrace();
				return false;
			}
		} else {
			return false;
		}
	}

	/**
	 * 
	 * Setzen den nï¿½chsten Spieler fï¿½r eine neue Runde. Der Spieler mit den
	 * wenigsten Punkten fï¿½ngt an.
	 * 
	 * 
	 * 
	 */

	public void nextRoundPlayer() {
//		System.out.println("nextRoundPlayer!");
		try {
		int players = this.getPlayerNumber();
		ArrayList<Integer> minOrSame = new ArrayList<Integer>();
		int MinScore = 99999;

		for (int i = 0; i < players; i++) {

			int PlayerPoints = this.getPlayerIngameKonto().get(i)
					.getGamePoints();

			if (MinScore > PlayerPoints) {
				MinScore = PlayerPoints;
				minOrSame.clear();
				minOrSame.add(i);
			} else if (MinScore == PlayerPoints) {
				minOrSame.add(i);
			}

		}
		if (minOrSame.size() == 1) {
			this.setCurrentPlayer(this.getPlayerIngameKonto()
					.get(minOrSame.get(0)).getPlayer());
			System.out.println("size=1");
		} else if (minOrSame.size() == 2) {
			int z = ((int) (Math.random() * 2 + 1)) - 1;
			this.setCurrentPlayer(this.getPlayerIngameKonto()
					.get(minOrSame.get(z)).getPlayer());
			System.out.println("size=2");

		} else if (minOrSame.size() == 3) {
			int z = ((int) (Math.random() * 3 + 1)) - 1;

			this.setCurrentPlayer(this.getPlayerIngameKonto()
					.get(minOrSame.get(z)).getPlayer());
			System.out.println("size=3");
		}
		} catch (Exception e) {
			e.printStackTrace();
		}

//		System.out.println("end next Roundplayer!");

	}

	// Konstruktor

	private mGame(@Attribute(name = "GameID") int GameID,
			@Element(name = "MaxPoints") int MaxPoints,
			@Element(name = "PlayerNumber") int PlayerNumber,
			@Element(name = "GameState") eGameState GameState,
			@Element(name = "GameName") String GameName,
			@Element(name = "CurrentPlayer") mPlayer CurrentPlayer,
			// @Element(name = "Winner")mPlayer Winner,
			// @ElementList(name="GameRoundList",
			// required=false)Stack<mGameRound> GameRoundList,
			@ElementList(name = "PlayerInGameKonto") ArrayList<mGameStat> PlayerInGameKonto
	// @Element(name="Chat")mChat chat
	) {
		super();
		this.GameID = GameID;
		this.MaxPoints = MaxPoints;
		this.PlayerNumber = PlayerNumber;
		this.GameState = GameState;
		this.GameName = GameName;
		this.PlayerInGameKonto = PlayerInGameKonto;
		this.CurrentPlayer = CurrentPlayer;

	}

	public eGameState getGameState() {
		return GameState;
	}

	public int getGameID() {
		return this.GameID;
	}

	public void setGameState(eGameState gameState) {
		GameState = gameState;
	}

	public boolean isGameRunning() {
		return (this.GameState == eGameState.GameIsRunning);
	}

	public boolean isGameFinished() {
		return (this.GameState == eGameState.GameIsFinished);
	}

	public boolean isWaitingForPlayers() {
		return (this.GameState == eGameState.waitingForPlayers);
	}

	/**
	 * 
	 * @param Player
	 * @return KontoNr
	 * 
	 * @see mGameTrickPlay.isFinishedTrick(mPlayer Player);
	 */

	public int getPlayerKontoNumber(mPlayer Player) {
		if (this.getPlayerIngameKonto() != null) {
			int i = 0; // War 1
			for (mGameStat gs : this.getPlayerIngameKonto()) {
				if (gs.getPlayer().equals(Player)) {
					return i;
				}
				i++;
			}
		}
		return 0;
	}

	public mGameStat getPlayerKonto(mPlayer Player) {
		if (this.getPlayerIngameKonto() != null) {
			for (mGameStat gs : this.getPlayerIngameKonto()) {
				if (gs.getHandCardsHolder().equals(Player)) {
					return gs;
				}
			}
		}
		return null;
	}

	public void addBet(String bet, mPlayer player) {
		if (this.getLastGameRound().getMoveNumber() < this.getPlayerNumber()) {
			this.getPlayerKonto(player).setBet(bet);
		}
	}

	public mGameStat getPlayerKontoByNumber(int Number) {
		if (this.getPlayerIngameKonto() != null) {
			return this.getPlayerList().get(Number - 1);
		}
		return null;
	}

	public ArrayList<mGameStat> getPlayerList() {
		return PlayerInGameKonto;
	}

	public int getPlayerNumber() {
		return PlayerNumber;
	}

	public int getMaxPoints() {
		return MaxPoints;
	}

	public Stack<mGameRound> getGameRoundList() {
		return GameRoundList;
	}

	public String getGameName() {
		return GameName;
	}

	public void setGameName(String GameName) {
		this.GameName = GameName;
	}
	/**
	 * 
	 * Die Methode überprüft, ob die geschickten Karten der Kombo, auch wirklich 
	 * den Handkarten des Spielers entsprechen. Dadurch werden Manipulationen verhindet
	 * 
	 * 
	 * @author David
	 * 
	 * @param Combo
	 * @param Player
	 * @return true - wenn alle Karten stimmen
	 * @return false - Wenn eine Karte nicht stimmt

	 */ 
	
	public boolean areValidCards(ArrayList<mCard> Combo, mPlayer Player){
		ArrayList<mCard> handCards = new ArrayList<mCard>();
				
		handCards.addAll(this.getPlayerIngameKonto().get(this.getPlayerKontoNumber(Player)).getPlayerCards());
		handCards.addAll(getPlayerIngameKonto().get(this.getPlayerKontoNumber(Player)).getJokerCards());
		boolean CardValid = false;
		
		for (int i = 0; i < Combo.size(); i++){
			CardValid = false;
			for (int n= 0; n < handCards.size(); n ++){
				if (Combo.get(i).equals(handCards.get(n))){
				CardValid = true;
				break;
				}
			}
			if (CardValid == false){
				return false;
			}
		}
		
		return true;
	}

	// public boolean addCardCombo(mCardCombo cc) {
	// return this.getLastTrickPlay().addCardCombo(cc);
	// }

	// private mGame(mGame cloneGame) {
	// super();
	// GameID = cloneGame.getGameID();
	// MaxPoints = cloneGame.getMaxPoints();
	// PlayerNumber = cloneGame.getPlayerNumber();
	// GameState = cloneGame.getGameState();
	// GameName = cloneGame.getGameName();
	// CurrentPlayer = cloneGame.getCurrentPlayer();
	// PlayerInGameKonto = cloneGame.getPlayerIngameKonto();
	// GameRoundList = cloneGame.getGameRoundList();
	// //CardListList = cloneGame.cardListList;
	// }

	// Erstellt von CHI und Thomas
	// public boolean newCardCombo(ArrayList<mCard> cardCombo, mPlayer player,
	// boolean strictGamePlay ) { // newCardCombo
	// System.out.println("CARDCOMBO2");
	// return this.getLastTrickPlay().newCardCombo(cardCombo, player,
	// strictGamePlay);
	//
	//
	// }

	// public mGame clone() throws CloneNotSupportedException {
	// return new mGame(this);
	// }
}
